<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Cpp expression question definition class.
 *
 * @package     cppexpression
 * @author      Khrzhanovskaya Olga, Sychev Oleg
 * @copyright   &copy; 2014 Oleg Sychev, Volgograd State Technical University
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */


defined('MOODLE_INTERNAL') || die();

global $CFG;

require_once($CFG->dirroot . '/question/type/questionbase.php');

/**
 * Represents a cpp expression question.
 *
 * @copyright  
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class qtype_cppexpression_question extends question_graded_automatically
        implements question_automatically_gradable, question_with_qtype_specific_hints {

    /** @var array of question_answer. */
    public $answers = array();

    /** @var string declarations of variables etc. */
    public $declarations;
    /** @var string grading analyzer name */
    public $gradinganalyzer;
    /** @var number only answers with fraction >= hintgradeborder would be used for hinting. */
    public $hintgradeborder;

    /* NOTE: For each new hint analyzer you should add two fields:
        - name().'mode' - for storing a mode (integer)
        - name().'penalty' - for storing penalty (float)
      */
    public $array2dmode;
    public $array2dpenalty;

    public $hintkeys = array();

    /** @var cache of best fit answer: keys in array are 'answer' and 'fitness'. */
    protected $bestfitanswer = array();
    /** @var reponse for which best fit answer is calculated as a string */
    protected $responseforbestfit = '';

    public function get_expected_data() {
        return array('answer' => PARAM_RAW_TRIMMED);
    }

    public function get_correct_response() {

        foreach ($this->answers as $answer) {
            if($answer->fraction == 1){
                return array('answer' => $answer->answer);
            }
        }
        return null;
    }

    public function get_matching_answer(array $response) {
        $bestfit = $this->get_best_fit_answer($response, $this->hintgradeborder);
        if ($bestfit['match'] == 1) {
            return $bestfit['answer'];
        }
        return array();
    }

    public function is_complete_response(array $response) {
        return array_key_exists('answer', $response) &&
            ($response['answer'] || $response['answer'] === '0');
    }

    public function is_gradable_response(array $response) {
        return $this->is_complete_response($response);
    }

    public function is_same_response(array $prevresponse, array $newresponse) {
        return question_utils::arrays_have_same_keys_and_values($prevresponse, $newresponse);
    }

    public function summarise_response(array $response) {
        if (isset($response['answer'])) {
            $resp = $response['answer'];
        } else {
            $resp = null;
        }
        return $resp;
    }

    public function get_validation_error(array $response) {
        if ($this->is_gradable_response($response)) {
            return '';
        }
        return get_string('pleaseenterananswer', 'qtype_shortanswer');
    }

    public function grade_response(array $response) {

        $grade = 0;
        $state = question_state::$gradedwrong;
        $bestfitanswer = $this->get_best_fit_answer($response);

        if ($bestfitanswer['match'] == 1) {
            $grade = $bestfitanswer['answer']->fraction;
            $state = question_state::graded_state_for_fraction($bestfitanswer['answer']->fraction);
        }
        return array($grade, $state);
    }

    public function available_specific_hints($response = null) {

        $hinttypes = array();

        if (count($this->hints) > 0) {
            $hinttypes[] = 'hintmoodle#';
        }

        $qtype = new qtype_cppexpression;

        $hintanalyzernames = $qtype->get_hint_analyzers_names();

        // Add each enabled analyzer with appropriate mode.
        foreach($hintanalyzernames as $hintanalyzername) {
            $fieldname = $hintanalyzername.'mode';
            if($this->$fieldname != 0) {// Mode 0 is turned off.
                $hinttypes[] = $hintanalyzername . '_' . $this->$fieldname;
            }
        }
        return $hinttypes;
    }

    /**
     * Hint object factory.
     *
     * Returns a hint object for given type.
     */
    public function hint_object($hintkey, $response = null) {
        // Moodle-specific hints
        if (substr($hintkey, 0, 11) == 'hintmoodle#') {
            return new qtype_poasquestion_hintmoodle($this, $hintkey);
        }

        // Create cppexpression analyzer hint.
        list($hintname, $mode) = explode('_', $hintkey);
        $classname = "\\qtype_cppexpression\\{$name}\\{$name}_hint";

        return new $classname($this, $hintkey, $mode);
    }

    public function get_best_fit_answer(array $response, $gradeborder = null) {
        // Check cache for valid results.
        if ($response['answer'] == $this->responseforbestfit && $this->bestfitanswer !== array() && $gradeborder === null) {
            return $this->bestfitanswer;
        }

        $graderclassname = "\\qtype_cppexpression\\{$this->gradinganalyzer}\\{$this->gradinganalyzer}_grader";
        $graderanalyzer = new $graderclassname;
        $bestfit = array('fitness' => -1, 'answer' => '');

        foreach($this->answers as $answer){
            $curanswerfitness = $graderanalyzer->fitness($answer->answer, $response['answer'], $this->declarations);
            if($curanswerfitness == 1){
                $bestfit['fitness'] = 1;
                $bestfit['answer'] = $answer;
                break;
            }
            if($bestfit['fitness'] < $curanswerfitness && $answer->fraction >= $this->hintgradeborder) {
                $bestfit['fitness'] = $curanswerfitness;
                $bestfit['answer'] = $answer;
            }
        }
        // Save best fitted answer for further uses (default grade border only).
        if ($gradeborder === null) {
            $this->bestfitanswer = $bestfit;
            $this->responseforbestfit = $response['answer'];
        }
        return $bestfit;
    }

    // We need adaptive or interactive behaviour to use hints.
    public function make_behaviour(question_attempt $qa, $preferredbehaviour) {
        global $CFG;

        if ($preferredbehaviour == 'adaptive' && file_exists($CFG->dirroot.'/question/behaviour/adaptivehints/')) {
             question_engine::load_behaviour_class('adaptivehints');
             return new qbehaviour_adaptivehints($qa, $preferredbehaviour);
        }

        if ($preferredbehaviour == 'adaptivenopenalty' && file_exists($CFG->dirroot.'/question/behaviour/adaptivehintsnopenalties/')) {
             question_engine::load_behaviour_class('adaptivehintsnopenalties');
             return new qbehaviour_adaptivehintsnopenalties($qa, $preferredbehaviour);
        }

        if ($preferredbehaviour == 'interactive' && file_exists($CFG->dirroot.'/question/behaviour/interactivehints/')) {
             question_engine::load_behaviour_class('interactivehints');
             return new qbehaviour_interactivehints($qa, $preferredbehaviour);
        }

        return parent::make_behaviour($qa, $preferredbehaviour);
    }
}